import CERTMT_sched as cert
#import npCERTMT_sched as np
import baseline_sched as base
#import MultiFrameNoSMT_sched as multi
#import OneFrameSizeSMT_sched as oneFrame
#import fullPreemption2_sched as fullPreempt
from random import *
import numpy as np
from gurobipy import *
import itertools

FAKE_ZERO = 0.00000000001
UMA_RESULT=0
FUNK_RESULT=1

# DISSERTATION new constants
FIXED = 0
EXPO = 1

BASELINE=1
#Preemptive CE scheduler
CERT_MT=2
#multiple frames, non-SMT tasks are preemptable
FAIL=3
RUNTIME = 3

class TaskSet:
    """Description String"""

    def __init__(self, setUtil, utilMin, utilMax, expected_score, slope, prob_harm, degree_harm, score_distro, core_count,
                 periodMin, periodCount, timeout, solutionLimit, lowerBound, upperBound, threadsPerTest):
        """Creates taskSet made of SmartTasks"""
        self.setUtil = setUtil
        self.utilMin = utilMin
        self.utilMax = utilMax
        self.expected_score = expected_score    #\ mu in dissertation
        self.prob_harm = prob_harm              # h in paper
        self.degree_harm = degree_harm          # r in paper
        self.score_distro = score_distro        # exponential or fixed
        self.m = core_count
        self.timeout = timeout
        self.threadsPerTest = threadsPerTest
        self.solutionLimit = solutionLimit
        self.lowerBound = lowerBound
        self.upperBound = upperBound
        self.periodMin = periodMin
        self.nTotal = 0
        self.slope = slope
        if prob_harm <=0 or prob_harm >= 1:
            # making everything harmful has same effect as making nothing harmful.
            self.harm_multiplier = 1
            self.standard_multiplier = 1
        else:
            self.standard_multiplier = 1 /(prob_harm * (degree_harm-1) +1 )
            self.harm_multiplier = self.standard_multiplier * degree_harm
        self.allTasks = []
        # DISSERTATION what does this do?

        # create periods
        self.hyperperiod = periodMin * 2 ** (periodCount - 1)
        self.periods = []

        # print("Creating periods")
        for i in range(periodCount):
            self.periods.append(periodMin * (2 ** i))
            # print(self.periods[i])

        # create tasks, but don't set s_{i:j} values yet
        self.totalUtil = 0
        permID = 0
        while self.totalUtil < setUtil:
            # create task
            util = random() * (utilMax - utilMin) + utilMin
            # DISSERTATION edits
            # these should be random functions
            baseline_intercept = weakness = np.random.exponential(expected_score)
            if random() <= prob_harm:
                harm = 1
            else:
                harm = 0
            period = choice(self.periods)
            self.allTasks.append(SmartTask(util, weakness, harm, period, self.slope, baseline_intercept, permID))
            self.totalUtil = self.totalUtil + util
            permID = permID + 1
        self.nTotal = permID

        # assign sym values
        for i in range(0, self.nTotal):
            task_i = self.allTasks[i]
            #task.symRaw = []
            #task.symAdj = []
            for j in range(0, self.nTotal):
                task_j = self.allTasks[j]
                # DISSERTATION
                if i==j:
                    task_i.symRaw.append(0)
                    task_i.symAdj.append(0)
                    task_i.allCosts.append(task_i.cost)
                else:
                    expected_score_pair = task_i.baseline_intercept + task_i.slope \
                    * (max(0, ((task_i.cost / task_j.cost)-1)))
                    if self.score_distro == FIXED:
                        task_i.symRaw.append(expected_score_pair)
                    elif self.score_distro == EXPO:
                        task_i.symRaw.append(np.random.exponential(expected_score_pair))
                    #print("task i " + str(i))
                    #print("task j " + str(j))
                    #print("score distro: " + str(self.score_distro))
                    #print("task i symRaw: " + str(task_i.symRaw))

                    # don't allow threading when costs differ by factor of 10+
                    ratio = task_i.cost / task_j.cost
                    if ratio> 10 or ratio < 0.1:
                        sym_adj = 10
                    else:
                        sym_adj = max(task_i.symRaw[j], FAKE_ZERO)
                    thread_cost_i = task_i.cost + sym_adj * min(task_i.cost, task_j.cost)
                    task_i.symAdj.append(sym_adj)
                    task_i.allCosts.append(thread_cost_i) 
            #print("Printing task " + str(i))
            #print(task_i)
            #print()


    def addTask(self):
        #self.nTotal=permID=len(self.allTasks)
        permID=len(self.allTasks)
        util = random() * (self.utilMax - self.utilMin) + self.utilMin
        # DISSERTATION
        baseline_intercept = weakness = np.random.exponential(self.expected_score)
        if random() <= self.prob_harm:
            harm = 1
        else:
            harm = 0
        # actually create the new task
        period = choice(self.periods)
        self.allTasks.append(SmartTask(util, weakness, harm, period, self.slope, baseline_intercept, permID))
        self.totalUtil = self.totalUtil + util
        #set sym values for the just-created task
        task_new = self.allTasks[permID]
        self.nTotal += 1
        # give task_new a cost for every already added task
        # self.nTotal = task_new.permID + 1, so task_new will be the last task in this loop
        for j in range(0, self.nTotal):
            task_old = self.allTasks[j]
            # DISSERTATION
            if task_new.permID == j:
                task_new.symRaw.append(0)
                task_new.symAdj.append(0)
                task_new.allCosts.append(task_new.cost)
            else:
                expected_score_new = task_new.baseline_intercept + task_new.slope \
                                      * max(0, (task_new.cost / task_old.cost - 1))
                expected_score_old = task_old.baseline_intercept + task_old.slope \
                                    * max(0, (task_old.cost /task_new.cost -1))
                if self.score_distro == FIXED:
                    task_new.symRaw.append(expected_score_new)
                    task_old.symRaw.append(expected_score_old)
                elif self.score_distro == EXPO:
                    task_new.symRaw.append(np.random.exponential(expected_score_new))
                    task_old.symRaw.append(np.random.exponential(expected_score_old))
                # don't allow threading when costs differ by factor of 10+
                ratio = task_new.cost / task_old.cost
                if ratio > 10 or ratio < 0.1:
                    sym_adj_new = 10
                    sym_adj_old = 10
                else:
                    sym_adj_new = max(task_new.symRaw[j], FAKE_ZERO)
                    sym_adj_old = max(task_old.symRaw[-1], FAKE_ZERO)
                thread_cost_new = task_new.cost + sym_adj_new * min(task_new.cost, task_old.cost)
                thread_cost_old = task_old.cost + sym_adj_old * min(task_new.cost, task_old.cost)
                task_new.symAdj.append(sym_adj_new)
                task_new.allCosts.append(thread_cost_new)
                task_old.symAdj.append(sym_adj_old)
                task_old.allCosts.append(thread_cost_old)


        '''
        # give the new task a threaded cost for each already existing task
        # self.nTotal is now 1 more than permID for the new task
        # the last task to be touched in this j loop is the task itself
        for j in range(0, self.nTotal):
            expected_score_pair = task.baseline_intercept + task.slope \
                                  * (max(0, (task.cost / self.allTasks[j].cost - 1)))
            if self.score_distro == FIXED:
                task.symRaw.append(expected_score_pair)
            elif self.score_distro == EXPO:
                task.symRaw.append(np.random.exponential(expected_score_pair))
            # random function based on expected value
            if permID == j:
                task.symAdj.append(0)
            elif task.symRaw[j] <= 0:
                task.symAdj.append(FAKE_ZERO)
            else:
                task.symAdj.append(task.symRaw[j])
            sym_adj = task.symAdj[-1]
            thread_cost = task.cost + sym_adj * min(task.cost, self.allTasks[j].cost)
            task.allCosts.append(thread_cost)

        # give each old task a cost to go with the new task
        # the last task in this loop is the one just before the new task
        for j in range(0, self.nTotal-1):
            old = self.allTasks[j]
            # used to find Cij where the old task is task i
            old_expected = old.baseline_intercept + old.slope \
                           * (max(0, old.cost/task.cost -1))

            if self.score_distro == FIXED:
                # DISSERTATION do I want to be appending here?
                # Yes; from j's perspective, the current task is the newest
                old.symRaw.append(old_expected)
            elif self.score_distro == EXPO:
                old.symRaw.append(np.random.exponential(old_expected))

            sym_adj = max(old.symRaw[-1], FAKE_ZERO)
            #avg = (task.cost + min(task.cost, self.allTasks[j].cost)) / 2
            #thread_cost = task.cost + sym_adj * avg
            
            
            #thread_cost = task.cost * (1+sym_adj)
            thread_cost = old.cost + sym_adj * min(task.cost, old.cost)
            old.symAdj.append(sym_adj)
            old.allCosts.append(thread_cost)
        '''
    def testSystem(self, runBaseline, runCertMT):
        if runBaseline:
            if self.totalUtil > self.m:
                successBaseline = False
            else:
                baseline = base.Baseline_sched(self)
                successBaseline = (baseline.schedule() == GRB.OPTIMAL)
        else:
            successBaseline = False

        if runCertMT:
            '''
            if partition:
                twoSystems=self.partition()
                sched1=cert.certMT_sched(twoSystems[0])
                sched2=cert.certMT_sched(twoSystems[2])
                successCertMT=(sched1.schedule()==GRB.OPTIMAL and sched2.schedule()==GRB.OPTIMAL)
            '''
            certMT = cert.certMT_sched(self)
            #successCertMT, runtime = (certMT.schedule() == GRB.OPTIMAL)
            #successCertMT = (certMT.schedule() == GRB.OPTIMAL)
            #runtime = certMT.solver.
            results = certMT.schedule()
            successCertMT = results[0] == GRB.OPTIMAL
            runtime = results [1]
            '''
            if successCertMT:
                print()
                self.printTaskSystem()
                certMT.printSolution()
                print()
            '''

        else:
            successCertMT = False

        # the negative one is there to help with indexing
        return -1, successBaseline, successCertMT, runtime
        #return -1, successBaseline, successCertMT


class SmartTask:
    """description string"""
    #harm: int

    def __init__(self, util, weakness, harm, period, slope, baseline_intercept, permID):
        self.util = float(util)
        self. weakness = weakness
        self.harm = harm
        self.symRaw = []  # will hold s_{i:j} values for all j; may be <0
        self.symAdj = []  # all values >= 0
        self.period = period
        self.cost = period * util
        self.permID = permID
        self.allCosts = []
        self.slope = slope
        self.baseline_intercept = baseline_intercept

    def __str__(self):
        strSymAdj = str(self.symAdj)
        strAllCosts = str(self.allCosts)
        #return str(str(self.permID) + " " + str(self.util) + " " + strSymAdj)
        return str(str(self.permID) + " w=" + str(self.weakness) + " h=" + str(self.harm) + "\n" +
                   "u=" + str(self.util) + " T=" + str(self.period) + " C=" + str(self.cost) + "\n" +
                   "Scores: \n" + strSymAdj + "\n" + "Costs: \n" +
                   strAllCosts)

        # every task needs a unique ID that holds across all partitions


#def __init__(self, setUtil, utilMin, utilMax, expected_score, slope, prob_harm, degree_harm, score_distro, core_count,
#                 periodMin, periodCount, timeout, solutionLimit, lowerBound, upperBound, threadsPerTest):
#myTaskSet = TaskSet(1, 0, 1, .5, .5, 0, 2, FIXED, 1,
#                    10, 1, 30, 1000, 1, 1, 0)
#                    10, 2, 0, 0, 0, 0, 0)
# print("standard multiplier " + str(myTaskSet.standard_multiplier))
# print("harmful multiplier " + str(myTaskSet.harm_multiplier))
# print("degree of harm " + str(myTaskSet.harm_multiplier/ myTaskSet.standard_multiplier))

#for task in myTaskSet.allTasks:
#    print(task)
#    print("")


